package com.platform.common.utils.spring;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;

import com.google.common.collect.Sets;
import com.platform.common.utils.log.LoggerFactory;

/**
 * <pre>
 * 功能：可以按照不同的运行模式启用相应的配置
 * </pre>
 * 
 * @author wei.gao
 */
@Component
public class BeanPropertyholderConfigurer extends PropertyPlaceholderConfigurer implements FactoryBean<Properties>, InitializingBean {
	
	public static final String ONLINE = "ONLINE";
	public static final String STAGE = "STAGE";
	public static final String TEST = "TEST";
	
	public static final Set<String> support_modes = Sets.newHashSet(ONLINE, STAGE, TEST);
	public static final String PRODUCTION_MODE = "production.mode";
	private static final String WORK_DIR_CONFIG = System.getProperty("user.dir") + "/config/production.properties";
	private static final String CLASSPATH_DIR_CONFIG = "config/production.properties";
	// 是否打开本地config配置, 会对引入的jar包中的配置做覆盖
	private static final String ENABLE_LOCAL_CONFIG = "local.config.enabled";
	// 配置文件存放路径
	private static final String PATHPATTERN = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/config/%s/*.properties";
	
	private boolean singleton = true;
	// 设置运行程序的模式，默认是开发模式，当进入测试或者发布上线就修改为其他模式
	private String mode = TEST;
	// 缓存所有的属性配置
	private Properties properties;
	// 配置文件存放路径
	private String pathPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/config/%s/*.properties";
	
	/**
	 * @return the mode
	 */
	public String getMode() {
		return mode;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		if (this.singleton) {
			this.properties = createProperties();
		}
	}
	
	private Properties createProperties() throws IOException {
		mode = initMode();
		
		pathPattern = String.format(PATHPATTERN, StringUtils.lowerCase(mode));
		PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		Resource[] userResources = resourcePatternResolver.getResources(pathPattern);
		
		List<Resource> resourceList = new ArrayList<Resource>();
		if (userResources != null) {
			resourceList.addAll(Arrays.asList(userResources));
		}
		
		super.setLocations(resourceList.toArray(new Resource[] {}));
		
		Properties result = this.mergeProperties();
		
		// 加载本地配置
		String localConfigEnabled = System.getProperty(ENABLE_LOCAL_CONFIG);
		if ("true".equalsIgnoreCase(localConfigEnabled)) {
			LoggerFactory.get().debug("local configuration enabled, try merge local properties");
			result = mergeLocalProperties(result);
		}
		
		LoggerFactory.get().debug("finished property configuration file initialization for production mode " + mode);
		return result;
	}
	
	private Properties mergeLocalProperties(Properties result) throws IOException {
		File configFolder = new File(System.getProperty("user.dir") + "/config/" + this.mode.toLowerCase());
		if (configFolder.exists() && configFolder.listFiles().length > 0) {
			for (String configFile : configFolder.list()) {
				LoggerFactory.get().debug("process local config file '" + configFile + "'");
				
				Properties customizedProperties = new Properties();
				customizedProperties.load(new FileInputStream(configFolder.getAbsolutePath() + "/" + configFile));
				result = mergeProperties(result, customizedProperties);
			}
		}
		
		return result;
	}
	
	/**
	 * <pre>
	 * 功能：初始化模式
	 * </pre>
	 * @return
	 * @throws IOException
	 */
	private String initMode() throws IOException {
		// 系统属性优先
		String mode = System.getProperty(PRODUCTION_MODE);
		if (StringUtils.isNotBlank(mode) && support_modes.contains(mode)) {
			LoggerFactory.get().info("loading system production config for {}", mode);
			return mode;
		}
		
		// 工作目录次之
		Properties workConfigProperties = PropertiesLoaderUtils.loadAllProperties(WORK_DIR_CONFIG);
		mode = workConfigProperties.getProperty(PRODUCTION_MODE);
		if (StringUtils.isNotBlank(mode) && support_modes.contains(mode)) {
			LoggerFactory.get().info("loading workspace production config for {}", mode);
			return mode;
		}
		
		// 类加载主目录再次之
		Properties classpathProperties = PropertiesLoaderUtils.loadAllProperties(CLASSPATH_DIR_CONFIG);
		mode = classpathProperties.getProperty(PRODUCTION_MODE);
		if (StringUtils.isNotBlank(mode) && support_modes.contains(mode)) {
			LoggerFactory.get().info("loading classpath production config for {}", mode);
			return mode;
		}
		
		// 默认Test
		mode = TEST;
		return mode;
	}
	
	public String getProperty(String key) {
		return resolvePlaceholder(key, properties);
	}
	
	@Override
	public Properties getObject() throws Exception {
		if (this.singleton) {
			return this.properties;
		} else {
			return createProperties();
		}
	}
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		try {
			Properties ex = this.mergeProperties();
			
			String localConfigEnabled = System.getProperty(ENABLE_LOCAL_CONFIG);
			if ("true".equalsIgnoreCase(localConfigEnabled)) {
				logger.debug("local configuration enabled, try merge local properties");
				ex = mergeLocalProperties(ex);
			}
			
			this.convertProperties(ex);
			this.processProperties(beanFactory, ex);
		} catch (IOException var3) {
			throw new BeanInitializationException("Could not load properties", var3);
		} catch (Exception exp) {
			new BeanInitializationException("Could not load properties", exp);
		}
	}
	
	@Override
	public Class<?> getObjectType() {
		return Properties.class;
	}
	
	@Override
	public boolean isSingleton() {
		return this.singleton;
	}
	
	private Properties mergeProperties(Properties... props) {
		if (null == props || props.length == 0) {
			return null;
		}
		Properties merged = new Properties();
		for (Properties prop : props) {
			if (null != prop && !prop.isEmpty()) {
				for (Object key : prop.keySet()) {
					merged.setProperty(key.toString(), prop.getProperty(key.toString()));
				}
			}
		}
		for (Object key : merged.keySet()) {
			LoggerFactory.get().info("Merged property. " + key + " = " + merged.get(key));
		}
		return merged;
	}
}
